package org.sqlproc.engine.impl;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.type.PrimitiveType;
import org.hibernate.type.Type;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The enumeration of all SQL META Types, which is the internal type. This type is used for the special processing of
 * the input/output value.
 * 
 * @author <a href="mailto:Vladimir.Hudec@gmail.com">Vladimir Hudec</a>
 */
enum SqlMetaType {
    /**
     * INT and INTEGER are related to Hibernate.INTEGER, Integer.class and int.class.
     */
    INT(Hibernate.INTEGER, new Class[] { Integer.class, int.class }, new String[] { "INT", "INTEGER" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, int.class, Integer.class);
            if (logger.isDebugEnabled())
                logger.debug("INT result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType + " "
                        + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.INTEGER);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("INT parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.INTEGER);
                } else {
                    query.setInteger(paramName, (Integer) inputValue);
                }
            }
        }
    },

    /**
     * LONG is related to Hibernate.LONG, Long.class and long.class.
     */
    LONG(Hibernate.LONG, new Class[] { Long.class, long.class }, new String[] { "LONG" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, long.class, Long.class);
            if (logger.isDebugEnabled())
                logger.debug("LONG result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.LONG);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("LONG parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.LONG);
                } else {
                    query.setLong(paramName, (Long) inputValue);
                }
            }
        }
    },

    /**
     * BYTE is related to BYTE, Byte and byte.class.
     */
    BYTE(Hibernate.BYTE, new Class[] { Byte.class, byte.class }, new String[] { "BYTE" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, byte.class, Byte.class);
            if (logger.isDebugEnabled())
                logger.debug("BYTE result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.BYTE);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("BYTE parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.BYTE);
                } else {
                    query.setByte(paramName, (Byte) inputValue);
                }
            }
        }
    },

    /**
     * BYTEARR and BYTEARRAY are related to Hibernate.BINARY, Byte[].class and byte[].class.
     */
    BYTE_ARRAY(Hibernate.BINARY, new Class[] { byte[].class, Byte[].class }, new String[] { "BYTEARR", "BYTEARRAY",
            "BYTES" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            if (logger.isDebugEnabled())
                logger.debug("BYTE_ARRAY result " + pojo + " " + attributeName + " " + resultValue + " "
                        + hibernateType);
            if (resultValue instanceof byte[]) {
                Method m = SqlUtils.getSetter(pojo, attributeName, byte[].class);
                if (m != null)
                    SqlUtils.invokeMethod(m, pojo, resultValue);
                else {
                    m = SqlUtils.getSetter(pojo, attributeName, Byte[].class);
                    if (m != null)
                        SqlUtils.invokeMethod(m, pojo, SqlUtils.toBytes((byte[]) resultValue));
                }
            } else if (resultValue instanceof Byte[]) {
                Method m = SqlUtils.getSetter(pojo, attributeName, Byte[].class);
                if (m != null)
                    SqlUtils.invokeMethod(m, pojo, resultValue);
            } else
                throw new RuntimeException("Incorrect binary array " + resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.BINARY);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("BYTE_ARRAY parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof byte[])
                    query.setBinary(paramName, (byte[]) inputValue);
                else if (inputValue instanceof Byte[]) {
                    query.setBinary(paramName, SqlUtils.toBytes((Byte[]) inputValue));
                } else
                    throw new RuntimeException("Incorrect binary array " + inputValue);
            }
        }
    },

    /**
     * SHORT is related to Hibernate.SHORT, Short.class and short.class.
     */
    SHORT(Hibernate.SHORT, new Class[] { Short.class, short.class }, new String[] { "SHORT" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, short.class, Short.class);
            if (logger.isDebugEnabled())
                logger.debug("SHORT result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.SHORT);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("SHORT parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.SHORT);
                } else {
                    query.setShort(paramName, (Short) inputValue);
                }
            }
        }
    },

    /**
     * STRING and STR are related to Hibernate.STRING and String.class.
     */
    STRING(Hibernate.STRING, new Class[] { String.class }, new String[] { "STRING", "STR" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, String.class);
            if (logger.isDebugEnabled())
                logger.debug("STRING result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.STRING);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("STRING parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.STRING);
                } else {
                    query.setString(paramName, (String) inputValue);
                }
            }
        }
    },

    /**
     * CHARACTER and CHAR are related to Hibernate.CHARACTER, Character.class and char.class.
     */
    CHAR(Hibernate.CHARACTER, new Class[] { Character.class, char.class }, new String[] { "CHARACTER", "CHAR" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, char.class, Character.class);
            if (logger.isDebugEnabled())
                logger.debug("CHAR result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.CHARACTER);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("CHAR parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.CHARACTER);
                } else {
                    query.setCharacter(paramName, (Character) inputValue);
                }
            }
        }
    },

    /**
     * DATE is related to Hibernate.DATE and java.util.Date.class.
     */
    DATE(Hibernate.DATE, new Class[] { java.util.Date.class }, new String[] { "DATE" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, java.util.Date.class);
            if (logger.isDebugEnabled())
                logger.debug("DATE result " + pojo + " " + attributeName + " " + resultValue.getClass() + " "
                        + resultValue + " " + hibernateType + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
            else {
                m = SqlUtils.getSetter(pojo, attributeName, LocalDate.class);
                if (m != null) {
                    LocalDate dt = new LocalDate(resultValue);
                    SqlUtils.invokeMethod(m, pojo, dt);
                }
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.DATE);
            } else {
                DateTime dt = new DateTime(inputValue).withTime(0, 0, 0, 0);
                if (logger.isDebugEnabled())
                    logger.debug("DATE parameter " + paramName + " " + inputValue + " " + hibernateType + " " + dt);
                query.setTimestamp(paramName, dt.toDate());
            }
        }
    },

    /**
     * FROMDATE is related to Hibernate.DATE and java.util.Date.class.
     */
    FROMDATE(Hibernate.DATE, new Class[] { java.util.Date.class }, new String[] { "FROMDATE" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            throw new UnsupportedOperationException();
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.DATE);
            } else {
                DateTime dt = new DateTime(inputValue).withTime(0, 0, 0, 0);
                if (logger.isDebugEnabled())
                    logger.debug("FROMDATE parameter " + paramName + " " + inputValue + " " + hibernateType + " " + dt);
                query.setTimestamp(paramName, dt.toDate());
            }
        }
    },

    /**
     * TODATE is related to Hibernate.DATE and java.util.Date.class.
     */
    TODATE(Hibernate.DATE, new Class[] { java.util.Date.class }, new String[] { "TODATE" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            throw new UnsupportedOperationException();
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.DATE);
            } else {
                DateTime dt = new DateTime(inputValue).withTime(0, 0, 0, 0).plusDays(1);
                if (logger.isDebugEnabled())
                    logger.debug("TODATE parameter " + paramName + " " + inputValue + " " + hibernateType + " " + dt);
                query.setTimestamp(paramName, dt.toDate());
            }
        }
    },

    /**
     * TIME is related to Hibernate.TIME and java.util.Date.class.
     */
    TIME(Hibernate.TIME, new Class[] { java.util.Date.class }, new String[] { "TIME" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, java.util.Date.class);
            if (logger.isDebugEnabled())
                logger.debug("TIME result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
            else {
                m = SqlUtils.getSetter(pojo, attributeName, LocalTime.class);
                if (m != null) {
                    LocalTime dt = new LocalTime(resultValue);
                    SqlUtils.invokeMethod(m, pojo, dt);
                }
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.TIME);
            } else {
                DateTime dt = new DateTime(inputValue).withMillisOfSecond(0);
                if (logger.isDebugEnabled())
                    logger.debug("TIME parameter " + paramName + " " + inputValue + " " + hibernateType + " " + dt);
                query.setTime(paramName, dt.toDate());
            }
        }
    },

    /**
     * DATETIME is related to Hibernate.TIMESTAMP and java.util.Date.class.
     */
    DATETIME(Hibernate.TIMESTAMP, new Class[] { java.util.Date.class }, new String[] { "DATETIME" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, java.util.Date.class);
            if (logger.isDebugEnabled())
                logger.debug("DATETIME result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null) {
                if (resultValue instanceof java.sql.Timestamp) {
                    ((java.sql.Timestamp) resultValue).setNanos(0);
                    SqlUtils.invokeMethod(m, pojo, resultValue);
                } else if (resultValue instanceof java.util.Date)
                    SqlUtils.invokeMethod(m, pojo, resultValue);
                else
                    throw new RuntimeException("Incorrect datetime " + resultValue);
            } else {
                m = SqlUtils.getSetter(pojo, attributeName, LocalDateTime.class);
                if (m != null) {
                    LocalDateTime dt = new LocalDateTime(resultValue);
                    SqlUtils.invokeMethod(m, pojo, dt);
                }
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.TIMESTAMP);
            } else {
                DateTime dt = new DateTime(inputValue).withMillisOfSecond(0);
                if (logger.isDebugEnabled())
                    logger.debug("DATETIME parameter " + paramName + " " + inputValue + " " + hibernateType + " " + dt);
                query.setTimestamp(paramName, dt.toDate());
            }
        }
    },

    /**
     * TIMESTAMP and STAMP are related to Hibernate.TIMESTAMP and java.sql.Timestamp.class.
     */
    TIMESTAMP(Hibernate.TIMESTAMP, new Class[] { java.sql.Timestamp.class }, new String[] { "TIMESTAMP", "STAMP" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, java.sql.Timestamp.class, java.util.Date.class);
            if (logger.isDebugEnabled())
                logger.debug("TIMESTAMP result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.TIMESTAMP);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("TIMESTAMP parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof java.sql.Timestamp) {
                    query.setTimestamp(paramName, (java.sql.Timestamp) inputValue);
                } else if (inputValue instanceof java.util.Date) {
                    query.setTimestamp(paramName, (java.util.Date) inputValue);
                } else
                    throw new RuntimeException("Incorrect timestamp " + inputValue);
            }
        }
    },

    /**
     * BOOLEAN and BOOL are related to Hibernate.BOOLEAN, Boolean.class and boolean.class.
     */
    BOOLEAN(Hibernate.BOOLEAN, new Class[] { Boolean.class, boolean.class }, new String[] { "BOOLEAN", "BOOL" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, boolean.class, Boolean.class);
            if (logger.isDebugEnabled())
                logger.debug("BOOLEAN result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.BOOLEAN);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("BOOLEAN parameter " + paramName + " " + inputValue + " " + hibernateType);
                query.setBoolean(paramName, (Boolean) inputValue);
            }
        }
    },

    /**
     * FLOAT is related to Hibernate.FLOAT, Float.class and float.class.
     */
    FLOAT(Hibernate.FLOAT, new Class[] { Float.class, float.class }, new String[] { "FLOAT" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, float.class, Float.class);
            if (logger.isDebugEnabled())
                logger.debug("FLOAT result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.FLOAT);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("FLOAT parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.FLOAT);
                } else {
                    query.setFloat(paramName, (Float) inputValue);
                }
            }
        }
    },

    /**
     * DOUBLE is related to Hibernate.DOUBLE, Double.class and double.class.
     */
    DOUBLE(Hibernate.DOUBLE, new Class[] { Double.class, double.class }, new String[] { "DOUBLE" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, double.class, Double.class);
            if (logger.isDebugEnabled())
                logger.debug("DOUBLE result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.DOUBLE);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("DOUBLE parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.DOUBLE);
                } else {
                    query.setDouble(paramName, (Double) inputValue);
                }
            }
        }
    },

    /**
     * BIGINT and BIGINTEGER are related to Hibernate.BIG_INTEGER and BigInteger.class.
     */
    BIG_INTEGER(Hibernate.BIG_INTEGER, new Class[] { BigInteger.class }, new String[] { "BIGINT", "BIGINTEGER" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, BigInteger.class);
            if (logger.isDebugEnabled())
                logger.debug("BIG_INTEGER result " + pojo + " " + attributeName + " " + resultValue + " "
                        + hibernateType + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.BIG_INTEGER);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("BIG_INTEGER parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.BIG_INTEGER);
                } else {
                    query.setBigInteger(paramName, (BigInteger) inputValue);
                }
            }
        }
    },

    /**
     * BIGDEC and BIGDECIMAL are related to Hibernate.BIG_DECIMAL and BigDecimal.class.
     */
    BIG_DECIMAL(Hibernate.BIG_DECIMAL, new Class[] { BigDecimal.class }, new String[] { "BIGDEC", "BIGDECIMAL" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, BigDecimal.class);
            if (logger.isDebugEnabled())
                logger.debug("BIG_DECIMAL result " + pojo + " " + attributeName + " " + resultValue + " "
                        + hibernateType + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.BIG_DECIMAL);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("BIG_DECIMAL parameter " + paramName + " " + inputValue + " " + hibernateType);
                if (inputValue instanceof Collection) {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray(), Hibernate.BIG_DECIMAL);
                } else {
                    query.setBigDecimal(paramName, (BigDecimal) inputValue);
                }
            }
        }
    },

    /**
     * ENUMSTRING and ESTRING are related to Hibernate.STRING.
     */
    ENUM_STRING(Hibernate.STRING, new Class[] {}, new String[] { "ENUMSTRING", "ESTRING" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getMethodIgnoreParameters(pojo.getClass(), SqlUtils.set(attributeName));
            if (logger.isDebugEnabled())
                logger.debug("ENUM_STRING result " + pojo + " " + attributeName + " " + resultValue + " "
                        + hibernateType + " " + m);
            if (m != null) {
                Method mm = SqlUtils.getMethodValueToEnum(m.getParameterTypes()[0], String.class);
                if (mm != null) {
                    Object enumInstance = SqlUtils.invokeMethod(mm, null, resultValue);
                    SqlUtils.invokeMethod(m, pojo, enumInstance);
                }
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.STRING);
            } else if (!inputValue.getClass().isEnum()) {
                if (!(inputValue instanceof Collection))
                    throw new RuntimeException("Incorrect enumeration " + inputValue);
                List<String> vals = new ArrayList<String>();
                for (Iterator iter = ((Collection) inputValue).iterator(); iter.hasNext();) {
                    Object val = iter.next();
                    if (!val.getClass().isEnum())
                        throw new RuntimeException("Incorrect enumeration in collection " + val);
                    Method m = SqlUtils.getMethodEnumToValue(val.getClass());
                    vals.add((String) SqlUtils.invokeMethod(m, val));
                }
                if (logger.isDebugEnabled())
                    logger.debug("ENUM_STRING parameter " + paramName + " " + inputValue + " " + inputValue.getClass()
                            + " " + hibernateType + " " + vals);
                query.setParameterList(paramName, vals.toArray());
            } else {
                Method m = SqlUtils.getMethodEnumToValue(inputValue.getClass());
                if (logger.isDebugEnabled())
                    logger.debug("ENUM_STRING parameter " + paramName + " " + inputValue + " " + inputValue.getClass()
                            + " " + hibernateType + " " + m);
                if (m != null)
                    query.setString(paramName, (String) SqlUtils.invokeMethod(m, inputValue));
            }
        }
    },

    /**
     * EINTEGER and EINT are related to Hibernate.INTEGER.
     */
    ENUM_INT(Hibernate.INTEGER, new Class[] {}, new String[] { "EINTEGER", "EINT" }) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getMethodIgnoreParameters(pojo.getClass(), SqlUtils.set(attributeName));
            if (logger.isDebugEnabled())
                logger.debug("ENUM_INT result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null) {
                Method mm = SqlUtils.getMethodValueToEnum(m.getParameterTypes()[0], Integer.class);
                if (mm != null) {
                    Object enumInstance = SqlUtils.invokeMethod(mm, null, resultValue);
                    SqlUtils.invokeMethod(m, pojo, enumInstance);
                }
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (inputValue == null) {
                query.setParameter(paramName, inputValue, Hibernate.INTEGER);
            } else if (!inputValue.getClass().isEnum()) {
                if (!(inputValue instanceof Collection))
                    throw new RuntimeException("Incorrect enumeration " + inputValue);
                List<Integer> vals = new ArrayList<Integer>();
                for (Iterator iter = ((Collection) inputValue).iterator(); iter.hasNext();) {
                    Object val = iter.next();
                    if (!val.getClass().isEnum())
                        throw new RuntimeException("Incorrect enumeration in collection " + val);
                    Method m = SqlUtils.getMethodEnumToValue(val.getClass());
                    vals.add((Integer) SqlUtils.invokeMethod(m, val));
                }
                if (logger.isDebugEnabled())
                    logger.debug("ENUM_INT parameter " + paramName + " " + inputValue + " " + inputValue.getClass()
                            + " " + hibernateType + " " + vals);
                query.setParameterList(paramName, vals.toArray());
            } else {
                Method m = SqlUtils.getMethodEnumToValue(inputValue.getClass());
                if (logger.isDebugEnabled())
                    logger.debug("ENUM_INT parameter " + paramName + " " + inputValue + " " + inputValue.getClass()
                            + " " + hibernateType + " " + m);
                if (m != null)
                    query.setInteger(paramName, (Integer) SqlUtils.invokeMethod(m, inputValue));
            }
        }
    },

    /**
     * It represents the Hibernate type.
     */
    HIBERNATE(null, new Class[] {}, new String[] {}) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            Method m = SqlUtils.getSetter(pojo, attributeName, hibernateType.getReturnedClass());
            if (m == null && hibernateType instanceof PrimitiveType)
                m = SqlUtils.getSetter(pojo, attributeName, ((PrimitiveType) hibernateType).getPrimitiveClass());
            if (m == null && hibernateType.getReturnedClass() == java.util.Date.class)
                m = SqlUtils.getSetter(pojo, attributeName, java.sql.Timestamp.class);
            if (logger.isDebugEnabled())
                logger.debug("HIBERNATE result " + pojo + " " + attributeName + " " + resultValue + " " + hibernateType
                        + " " + m);
            if (m != null)
                SqlUtils.invokeMethod(m, pojo, resultValue);
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            if (logger.isDebugEnabled())
                logger.debug("HIBERNATE parameter " + paramName + " " + inputValue + " " + hibernateType);
            if (inputValue instanceof Collection) {
                query.setParameterList(paramName, ((Collection) inputValue).toArray(), hibernateType);
            } else {
                query.setParameter(paramName, inputValue, hibernateType);
            }
        }
    },

    /**
     * It represents the type derived from Java attribute class.
     */
    DEFAULT(null, new Class[] {}, new String[] {}) {
        public void setResult(Object pojo, String attributeName, Object resultValue, Type hibernateType) {
            try {
                Class<?> attributeType = SqlUtils.getFieldType(pojo.getClass(), attributeName);
                if (attributeType.isEnum()) {
                    Method m = SqlUtils.getMethodIgnoreParameters(pojo.getClass(), SqlUtils.set(attributeName));
                    if (logger.isDebugEnabled())
                        logger.debug("ENUM result " + pojo + " " + attributeName + " " + resultValue + " "
                                + hibernateType + " " + m);
                    if (m != null) {
                        Method mm = SqlUtils.getMethodValueToEnum(m.getParameterTypes()[0]);
                        if (mm != null) {
                            Object enumInstance = SqlUtils.invokeMethod(mm, null, resultValue);
                            SqlUtils.invokeMethod(m, pojo, enumInstance);
                        }
                    }
                } else {
                    Method m = SqlUtils.getSetter(pojo, attributeName, attributeType);
                    if (logger.isDebugEnabled())
                        logger.debug("DEFAULT result " + pojo + " " + attributeName + " " + resultValue + " "
                                + hibernateType + " " + m);
                    if (m != null)
                        SqlUtils.invokeMethod(m, pojo, resultValue);
                }
            } catch (SecurityException e) {
                throw new RuntimeException(e);
            }
        }

        void setParameter(Query query, String paramName, Object inputValue, Type hibernateType) {
            Class<?> clazz = inputValue.getClass();
            if (!(inputValue instanceof Collection)) {
                if (clazz.isEnum()) {
                    Method m = SqlUtils.getMethodEnumToValue(clazz);
                    if (logger.isDebugEnabled())
                        logger.debug("ENUM parameter " + paramName + " " + inputValue + " " + inputValue.getClass()
                                + " " + hibernateType + " " + m);
                    if (m != null) {
                        if (m.getReturnType() == Integer.class || m.getReturnType() == int.class)
                            query.setInteger(paramName, (Integer) SqlUtils.invokeMethod(m, inputValue));
                        else
                            query.setString(paramName, (String) SqlUtils.invokeMethod(m, inputValue));
                    }
                } else {
                    SqlMetaType type = classToTypeMap.get(clazz);
                    if (logger.isDebugEnabled())
                        logger.debug("DEFAULT parameter " + paramName + " " + inputValue + " "
                                + ((inputValue != null) ? inputValue.getClass() : "-") + " " + hibernateType + " "
                                + type);
                    if (type != null)
                        type.setParameter(query, paramName, inputValue, hibernateType);
                }
            } else {
                List<Object> vals = new ArrayList<Object>();
                boolean isEnum = false;
                for (Iterator iter = ((Collection) inputValue).iterator(); iter.hasNext();) {
                    Object val = iter.next();
                    if (!val.getClass().isEnum())
                        break;
                    else
                        isEnum = true;
                    Method m = SqlUtils.getMethodEnumToValue(val.getClass());
                    vals.add(SqlUtils.invokeMethod(m, val));
                }
                if (logger.isDebugEnabled())
                    logger.debug("DEFAULT collection parameter " + paramName + " " + inputValue + " "
                            + inputValue.getClass() + " " + hibernateType + " " + vals);
                if (isEnum) {
                    query.setParameterList(paramName, vals.toArray());
                } else {
                    query.setParameterList(paramName, ((Collection) inputValue).toArray());
                }
            }
        }
    };

    /**
     * The internal type, which means special processing of the input/output value.
     */
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * The Hibernate type. A standard way to assing the type of parameter/scalar binding to the Hibernate Query.
     */
    private Type hibernateType;
    /**
     * The list of Java class type;
     */
    private Class<?>[] classTypes;
    /**
     * The list of String representation of the internal types, which means special processing of the input/output
     * value.
     */
    private String[] metaTypes;

    /**
     * Returns the Hibernate type. A standard way to assing the type of parameter/scalar binding to the Hibernate Query.
     * 
     * @return the Hibernate type
     */
    Type getHibernateType() {
        return hibernateType;
    }

    /**
     * Creates a new instance.
     * 
     * @param hibernateType
     *            the Hibernate type
     * @param classTypes
     *            the list of Java class type
     * @param myTypes
     *            the list of String representation of the internal types
     */
    private SqlMetaType(Type hibernateType, Class<?>[] classTypes, String[] metaTypes) {
        this.hibernateType = hibernateType;
        this.classTypes = classTypes;
        this.metaTypes = metaTypes;
    }

    /**
     * The map between a Java class types and an internal types.
     */
    static Map<Class<?>, SqlMetaType> classToTypeMap = new HashMap<Class<?>, SqlMetaType>();
    static {
        for (SqlMetaType value : SqlMetaType.values()) {
            for (Class<?> clazz : value.classTypes)
                classToTypeMap.put(clazz, value);
        }
    }

    /**
     * The map between a String representation of an internal types and an internal types.
     */
    static Map<String, SqlMetaType> metaToTypeMap = new HashMap<String, SqlMetaType>();
    static {
        for (SqlMetaType value : SqlMetaType.values()) {
            for (String metaType : value.metaTypes)
                metaToTypeMap.put(metaType, value);
        }
    }

    /**
     * Initializes the attribute of the result class with output values from SQL query execution.
     * 
     * @param resultInstance
     *            the instance of the result class
     * @param attributeName
     *            the name of the attribute in the result class
     * @param resultValue
     *            Query execution output value
     * @param hibernateType
     *            the Hibernate type
     */
    abstract void setResult(Object resultInstance, String attributeName, Object resultValue, Type hibernateType);

    /**
     * Bind an input value to a named query parameter.
     * 
     * @param query
     *            the object-oriented representation of a Hibernate query
     * @param paramName
     *            the name of the parameter
     * @param inputValue
     *            the possibly-null parameter value, a dynamic input value
     * @param hibernateType
     *            the Hibernate type
     */
    abstract void setParameter(Query query, String paramName, Object inputValue, Type hibernateType);
}
